/**
 * Created by hjqi on 2016/5/1.
 */


var base = require('./storageBase');
var util = require('util');
var path = require('path');
var logger = require('../log4js/log4js').logger('normal');
var formurlencoded = require('form-urlencoded');
var Q = require('q'),
    result = Q('begin');

var config = require('../config/config').config;
var _restapi = config.gluster_restful_api;
var _glfsRootDir = config.glusterfsMeta.rootDir;

var httpRequest = require('../common/httpRequest').httpRequest;
var _req = new httpRequest();

var Common = require('../common/common');
var uuid = require('../common/common').generateUUID;
var myRet = Common.myReturn;
var emptyPromise = Common.emptyPromise;


var storageMetaInfoDB = require('../../db/storageMetaInfoDB');
var _storageinfo = new storageMetaInfoDB();


var PythonShell = require('python-shell');

module.exports = libGlusterFs;

function libGlusterFs(){
    base.call(this);
}

/**
 * create storage endpoint in k8s and create volume
 * @param   {String}    ns     = namespace in k8s
 * @param   {String}    volume    = volume object  {mountName: xxx,  volumeName:xxx }
 */
libGlusterFs.prototype.createVolume = function(ns,vol,policy){
    var self = this;
    var _k8s_adp = require('../k8s/k8sAdpter');
    return new Promise(function (resolve, reject) {
        //get k8s object
        _k8s_adp.getK8s('default').then(function(_k8s){
            //create glusterfs service in own namespace
            return _k8s.createStorageSvcInOwnNS('default',[vol.volumeName]);
        }).then(function(){
            //create gluster volume
            return self.createVolumeImpl(ns,vol,policy);
        }).then(function(ret){
            //successful
            resolve(ret);
        }).catch(function(err){
            //exception
            reject(err);
        })
    });
}

/**
 * check volume if exist in glusterfs server
 * @param   {String}    volumeId    = volume id
 */
libGlusterFs.prototype.isExistVolumeInServer = function(volumeId){
    return new Promise(function (resolve, reject) {
        var _url = "http://"+_restapi.host+":"+_restapi.port+"/api/1.0/volume/"+volumeId;
        var _httpBasicHeader = {
            Authorization:'Basic '+_getAuth(_restapi.user,_restapi.password)
        }
        _req._request(_url,"GET",_httpBasicHeader,'',function(data, result, response){
            if (data !== null ){
                if(data instanceof Error){
                    reject(myRet(false,data.message));
                }else{
                    var _json = JSON.parse(data.data);
                    resolve(myRet(_json.ok));
                }
            }
            else{
                reject(myRet(false,result));
            }
        });
    });
}

/**
 *
 * @param ns
 * @param app
 * @param volObj
 * @param replicaNum
 * @param start
 * @param statefulPolicy
 */
libGlusterFs.prototype.createStatefulVolume = function(ns, app, volObj, replicaNum, start,statefulPolicy){
    var promises = [];
    var startValue = start ? start : 0;
    var encodeMountPath = "";
    var volNamePrefix = "";
    var isSpecVolume = true;
    /**
     *  mount specially volume ,so mountObj prop is not {}
     */
    if(!Common.isEmptyObject(volObj.mountObj) && volObj.mountObj.name !== undefined){
        volNamePrefix = volObj.mountObj.name
    }else{
        isSpecVolume = false;
        encodeMountPath = Common.encodePathToVolname(volObj.mountTargetPath);
        volNamePrefix = app + encodeMountPath;
    }

    //default policy is reuse
    var _policy = statefulPolicy ? statefulPolicy:Common.PVPolicy.REUSE;
    for (var i = startValue; i < replicaNum; i++) {
        var vol = "";
        if(isSpecVolume && i === 0)
            vol = volNamePrefix;
        else
            vol = volNamePrefix+"-"+i;
        promises.push(this.createVolume(ns, vol,_policy));
    }
    return Promise.all(promises);
}

libGlusterFs.prototype.genVolumeName = function(app){
    return
}

/**
 * check volume name in glusterfs
 * @param   {String}    ns     = namespace in k8s
 * @param   {String}    volume    = volume name
 */
libGlusterFs.prototype.chkNameExist = function(ns,volume){
    //call internal check function
    return _isExistVolumeInServer(ns+'-'+volume);
}

/**
 * only delete volume in glusterfs
 * @param   {String}    ns     = namespace in k8s (if volumeId is unique ,ns is useless)
 * @param   {String}    volumeId    = volume id
 */
libGlusterFs.prototype.deleteVolumeForce = function(ns,volumeId){

    return new Promise(function (resolve, reject) {

        //check if exist volume exist in glusterfs
        _isExistVolumeInServer(volumeId).then(function(ret){
            //if exist , delete it
            if(ret.ret){
                return _deleteVolume(volumeId);
            }
            //not exist ,nothing to do and go to next step
            else{
                return new Promise(function (_resolve,_reject) {
                    _resolve(myRet(true)) ;
                });
            }
        }).then(function(ret){
            //if delete volume successful, delete db record
            if (ret.ret === true) {
                _storageinfo.delWithParam(null,{namespace:ns,volumeId:volumeId},function(err){
                    if(err){
                        reject(myRet(false,err.message));
                    }
                    else{
                        resolve(myRet(true));
                    }
                });
            }
            //if delete failed,throw error
            else{
                reject(ret);
            }
        })
        //catch delete volume exception
        .catch(function(err){
            reject(err);
        });

    });
}


libGlusterFs.prototype.deleteStatefulVolumeForce = function(ns, app, encodeMountPath, replicaNum){
    var promises = [];
    for (var i = 0; i < replicaNum; i++) {
        var vol = app + "-" + i + "-" + encodeMountPath;
        promises.push(this.deleteVolumeForce(ns, vol));
    }
    return Promise.all(promises);
}

libGlusterFs.prototype.deleteStatefulVolumeByScale = function(ns, app, encodeMountPath, replicaNum,start){
    var promises = [];
    var startValue = start ? start : 0;
    for (var i = startValue; i < replicaNum; i++) {
        var vol = ns+"-"+ app + "-" + i + encodeMountPath;
        //for rollback call,so use prototype instand this
        promises.push(libGlusterFs.prototype.deleteVolumeForce(ns, vol));
    }
    return Promise.all(promises);
}

/**
 * delete volume  in glusterfs include volume service ,volume job (data-volume explorer)
 * @param   {String}    ns     = namespace in k8s
 * @param   {Object}    volItem    = volume name
 *
 * //volItem
 * {
     appsMount = ""
     mountAppName = ""
     mountNodeName = ""
     name = "sdfff"
     namespace = "hjqi6"
     size = 1024
     status = "NOT_USE"
     volumeId = "hjqi6-sdfff"
     _id = "58203f9d2e6aed1d600b1178"
 }
 *
 */
libGlusterFs.prototype.deleteVolume = function(volItem){

    var _self = this;
    var ns = volItem.namespace;
    return new Promise(function (resolve, reject) {
        var _volId = volItem.volumeId;
        var svcId = volItem.svcId;
        var _rollback_stack = [];
        var _p  = undefined;
        //first delete this volume service in k8s
        if(svcId !== undefined){
            _p = _deleteVolService(svcId).then(function(){

                _rollback_stack.push({
                    fn:_createVolService,
                    args:[volItem.service]
                });

                // then delete volume job in k8s
                return _deleteVolJob(svcId);
            }).then(function(){

                _rollback_stack.push({
                    fn:_createVolJob,
                    args:[svcId,volItem.job]
                });

                //then delete volume in glusterfs
                return _deleteVolume(_volId);
            });
        }else{
            _p = _deleteVolume(_volId);
        }
        _p.then(function(ret){
            //then delete record in db
            return new Promise(function (_resolve, _reject) {
                if (ret.ret === true) {
                    _storageinfo.delWithParam(null, {namespace: ns, volumeId: _volId}, function (err) {
                        if (err) {
                            _reject(myRet(false,err.message));
                        }
                        else {
                            _resolve(myRet(true));
                        }
                    });
                } else {
                    _reject(myRet(false));
                }
            });
        }).then(function(ret){
            //finish all process
            resolve(ret);
        }).catch(function(err){
            //rollback
            rollback(_self,_rollback_stack).then(function(){
                reject(err);
            }).catch(function(err){
                //rollback happened error
                //logger.info("delete volume rollback err:"+JSON.stringify(err));
                reject(err);
            });
        });
    });

}

/**
 * create volume in glusterfs server
 * @param   {String}    volumeId    = volume id
 */
libGlusterFs.prototype.createVolumeReq = function(ns,volumeId){
    return new Promise(function (resolve, reject) {
        console.time('_getActiveNodelist');
        //get 2 active node from glusterfs servers
        _getActiveNodelist().then(function(ret){

            //replica is 2
            if(ret.ret === true && ret.data.length > 1){
                var nodes = ret.data;

                var _url = "http://" + _restapi.host + ":" + _restapi.port + "/api/1.0/volume/" + volumeId;
                var _httpBasicHeader = {
                    Authorization:'Basic '+_getAuth(_restapi.user,_restapi.password),
                    "Content-Type": 'application/x-www-form-urlencoded',
                    "Connection":"keep-alive"
                }
                var _postData = {
                    bricks: _getBricks(nodes, volumeId),
                    start: 1,
                    force:1,
                    replica: 2
                };
                _req._request(_url, "POST", _httpBasicHeader, formurlencoded(_postData), function (data, result, response) {
                    if (data !== null) {
                        //socket hang up
                        if(data.data === undefined)
                            console.log(data);
                        else{
                            var _obj = JSON.parse(data.data);
                            if(_obj.ok === true){
                                resolve(myRet(true));
                            }else{
                                logger.error(_obj.error);
                                reject(myRet(false,_obj.error));
                            }
                        }

                    }
                    else {
                        reject(myRet(false,result));
                    }
                });

            }else
                reject(myRet(false,'volume server active node is less than 2'));

        }).catch(function(err){
            reject(err)
        });

    });
}

/**
 * delete volume in glusterfs server
 * @param   {String}    volumeId    = volume id
 */
function _deleteVolume(volumeId){
    return new Promise(function (resolve, reject) {
        //cleanup files in volume
        _delFilesInVol(volumeId).then(function(ret){
            //success, then delete volume
            if(ret.ret === true){
                return _stopVol(volumeId);
            }
            //failed,throw error (maybe still need to delete volume)
            else
                reject(ret);
        }).then(function(ret){
            return _delVol(volumeId);
        }).then(function(ret){
            resolve(ret);
        }).catch(function(err){
            reject(err);
        });
    });
}

/**
 * cleanup files in volume when volume delete
 * @param   {String}    volumeId    = volume id
 */
function _delFilesInVol(volumeId){
    return new Promise(function (resolve, reject) {
        //make sure at least one node is active
        _getActiveNodelist().then(function(ret) {

            //assume that success
            //var nodes = ret.data;
            if(ret.data.length > 0) {

                var options = {
                    args: [ret.data[0], volumeId ]
                };
                try{
                    PythonShell.run('./python/libgfapi-python-wrapper.py', options, function (err, results) {
                        if (err){
                            if(err.message.indexOf('libgfapi.so not found') != -1)
                                resolve(myRet(true,err.message));
                            else{
                                var _retmsg = "clean up files in volume ["+volumeId+"] error:"+err.message;
                                logger.error("delete volume file error:"+_retmsg);
                                reject(myRet(false,_retmsg));
                            }
                        }
                        else{
                            results = results || [];
                            if(results[0] === "true"){
                                resolve(myRet(true));
                            }else{

                                if(results[0].indexOf("glfs_init") !== -1 && results[0].indexOf("No such file or directory")){
                                    logger.warn("delete volume warn:",results[0]);
                                    resolve(myRet(true));
                                }else{
                                    logger.error("delete volume file error:"+results.join("\n"));
                                    reject(myRet(false,results.join("\n")));
                                }

                            }
                        }
                    });
                }catch (e){
                    if(e.message.indexOf('libgfapi.so not found') != -1){
                        resolve(myRet(true,e.message));
                    }else
                        reject(myRet(false,e.message));
                }
            }
            else{
                var _retmsg = "no active glusterfs server";
                reject(myRet(false,_retmsg));
            }
        }).catch(function(err){
            if(err instanceof Error){
                logger.error("delete volume error:",err.message);
                reject(myRet(false,err.message));
            }else{
                reject(err);
            }
        });

    });
}

/**
 *  stop volume first before delete it
 * @param volumeId
 * @private
 */
function _stopVol(volumeId){

    return new Promise(function (resolve, reject) {

        var _url = "http://" + _restapi.host + ":" + _restapi.port + "/api/1.0/volume/" + volumeId+"/stop";
        var _httpBasicHeader = {
            Authorization:'Basic '+_getAuth(_restapi.user,_restapi.password),
            "Content-Type": 'application/x-www-form-urlencoded',
            "Connection":"keep-alive"
        }
        var _postData = {
            force: 1
        };

        _req._request(_url, "PUT", _httpBasicHeader, formurlencoded(_postData), function (data, result, response) {
            if (data !== null) {
                var _ret = JSON.parse(data.data);
                //allow return true when delete not exist
                if(_ret.ok === false)
                    resolve(myRet(true));
                else{
                    //return logic error
                    resolve(myRet(_ret.data));
                }
            }
            else {
                reject(myRet(false,result));
            }
        });
    });
}

/**
 * delete volume in gluster server
 * @param   {String}    volumeId    = volume id
 */
function _delVol(volumeId){
    return new Promise(function (resolve, reject) {

        var _url = "http://" + _restapi.host + ":" + _restapi.port + "/api/1.0/volume/" + volumeId;
        var _httpBasicHeader = {
            Authorization:'Basic '+_getAuth(_restapi.user,_restapi.password),
            "Content-Type": 'application/x-www-form-urlencoded',
            "Connection":"keep-alive"
        }
        var _postData = {
            stop: 1
        };

        _req._request(_url, "DELETE", _httpBasicHeader, formurlencoded(_postData), function (data, result, response) {
            if (data !== null) {
                var _ret = JSON.parse(data.data);
                //allow return true when delete not exist
                if(_ret.ok === false)
                    resolve(myRet(true));
                else{
                    //return logic error
                    resolve(myRet(_ret.data));
                }
            }
            else {
                reject(myRet(false,result));
            }
        });
    });
}

/**
 * create volume service in k8s
 * @param   {Object}    svc    = volume service object
 */
function _createVolService(svc){

    //storage use default cluster
    //temporary put volume service in default cluster
    var _k8s_adp = require('../k8s/k8sAdpter');

    return new Promise(function (resolve, reject) {
        if(svc === undefined){
            resolve(myRet(true));
        }
        else{
            _k8s_adp.getK8s('default').then(function(_k8s){
                //create volume service in default namespace in k8s
                return _k8s.createSvcOnly('default',svc);
            }).then(function(ret){
                return resolve(ret);
            }).catch(function(err){
                logger.error("k8s load cluster info happened error"+err.error);
                reject(err);
            });
        }
    });
}

/**
 * delete volume service in k8s
 * @param   {String}    svcId    = volume service id
 */
function _deleteVolService(svcId){

    var _k8s_adp = require('../k8s/k8sAdpter');

    return new Promise(function (resolve, reject) {
        if(svcId === undefined)
            resolve(myRet(true));
        else{

            _k8s_adp.getK8s('default').then(function(_k8s){
                //delete service in k8s
                return _k8s.deleteSvcOnly('default',svcId);
            }).then(function(ret){
                resolve(ret);
            }).catch(function(err){
                logger.error("k8s load cluster info happened error:"+err.error);
                reject(err);
            });
        }
    });
}

/**
 * create volume job in k8s
 * @param   {String}    svcId    = volume service id
 * @param   {Object}    job    = volume job object
 */
function _createVolJob(svcId,job){

    var _k8s_adp = require('../k8s/k8sAdpter');

    return new Promise(function (resolve, reject) {
        if(svc === undefined)
            resolve(true);
        else{
            _k8s_adp.getK8s('default').then(function(_k8s) {
                //create volume job in k8s
                return _k8s.createJob(svcId, job);
            }).then(function(ret){
                resolve(ret);
            }).catch(function(err){
                //logger.error("k8s load cluster info happened error"+err);
                reject(err);
            });

        }
    });
}

/**
 * delete volume job in k8s
 * @param   {String}    svcId    = volume service id
 */
function _deleteVolJob(svcId){

    var _k8s_adp = require('../k8s/k8sAdpter');

    return new Promise(function (resolve, reject) {
        if(svcId === undefined){
            resolve(myRet(true));
        }
        else{
            _k8s_adp.getK8s('default').then(function(_k8s) {
                //delete k8s job in k8s
                return _k8s.deleteJob(svcId);
            }).then(function(ret){

                if(ret.ret === true)
                    resolve(ret);
                else{
                    reject(myRet(false,'delete volume job error'));
                }
            }).catch(function(err){
                //logger.error(err.error);
                reject(err);
            });
        }
    });
}

/**
 * get active node from gluster server
 */
function _getActiveNodelist(){
    return  new Promise(function (resolve, reject) {
        var _url = "http://" + _restapi.host + ":" + _restapi.port + "/api/1.0/peers";
        var _httpBasicHeader = {
            Authorization:'Basic '+_getAuth(_restapi.user,_restapi.password),
            "Content-Type": 'application/x-www-form-urlencoded',
            "Connection":"keep-alive"
        }
        _req._request(_url, "GET", _httpBasicHeader,'', function (data, result, response) {
            if(data instanceof Error){
                reject(myRet(false,data.message));
            }
            else if (data !== null) {
                var _ret = JSON.parse(data.data);
                if(_ret.ok === true){
                    var _list = [];

                    //gluster server will return localhost if http request on that node,
                    // so need to change hostname from config
                    var _localName = _getLocalName(_ret.data);
                    _ret.data.forEach(function(item){

                        if(item.name === 'localhost')
                            _list.push(_localName);
                        else
                            _list.push(item.name);

                    });
                    resolve(myRet(true,undefined,_list));
                }else{
                    reject(myRet(false,_ret.data));
                }
            }
            //return http response
            else {
                reject(myRet(false,result));
            }
        });

        //find localhost hostname from config
        function _getLocalName(rets){
            var _namesPool = config.glusterfsMeta.names.slice();
            rets.forEach(function(item){
                if(item.name !== 'localhost'){
                    var _idx = _namesPool.indexOf(item.name);
                    if(_idx !== -1)
                        _namesPool.splice(_idx,1);
                }
            });
            return _namesPool[0];
        }
    });
}

/**
 * construct auth field in http header
 * @param   {String}    user    = username
 * @param   {String}    pswd    = password
 */
function _getAuth(user,pswd){
    return  new Buffer(user+":"+pswd).toString('base64');
}

/**
 * construct bricks string from active nodes
 * @param   {Array}    nodes    = gluster server node
 * @param   {String}    volumeId    = volume id
 */
function _getBricks(nodes,volumeId){
    var _ret = [];
    var _len = nodes.length;
    for(var i=0; i<_len;i++){
        var _cur = nodes[i];
        _ret.push(_cur+':'+_glfsRootDir+'/'+volumeId);
    }
    return _ret.join(',');
}

/**
 * rollback function (maybe in should put in common part)
 * @param   {Array}    ctx    = self object
 * @param   {Array}    stack    = rollback operators stack
 */
function rollback(ctx,stack){

    return new Promise(function(resolve, reject){
        stack.reverse().forEach(function(exec){
            var fn = exec.fn;
            result = result.then(fn.apply(ctx,exec.args));
        });


        result.then(function (finalText) {
                resolve(true);
            })
            .catch(function (error) {
                reject(error);
            })
            .done();
    });

}

util.inherits(libGlusterFs,base);